import Web3 from 'web3';
import { ethNodeUrl } from '../../../global';
import { toHex, AbiItem } from 'web3-utils';
import dec from 'decimal.js';
import { logger } from '../../../models/LogModel';
import Tx from 'ethereumjs-tx';
import { Contract } from 'web3-eth-contract/types';
import { ExtractMessage, TransactionMessage } from '../../../Interface/TransactionMessage';
import { EthCoinType, EthConfig, EthTransfer } from '../../../database/eth/EthDatabase';
import { post } from 'request';
import getAddressNonce from '../../../lib/getAddressNonce';

class EthTokenExtract {

  static web3 = new Web3(ethNodeUrl)
  private transferToAddress: string;
  private transferToAmount: string|number;
  private contract: Contract;
  private contractAddress: string;
  private decimal: number;
  private tokenName: string;
  private orderId: string | number;

  constructor(to: string, amount: number|string, contractAddress: string, orderId: string| number) {
    this.transferToAddress = to;
    this.contractAddress = contractAddress;
    this.transferToAmount = amount.toString();
    this.orderId = orderId;
  }
  
  async init() {
    this.decimal = await EthCoinType.getContractDecimal(this.contractAddress);
    this.transferToAmount = new dec(this.transferToAmount).mul(10**this.decimal).toFixed();
    this.tokenName = await EthCoinType.getContractName(this.contractAddress);
  }
  
  /**
   * 获取合约实例链接
   * @memberof EthExtract
   */
  async getTokenClient() {
    const abi = await this.getAbi();
    this.contract = new EthTokenExtract.web3.eth.Contract(abi, this.contractAddress);
  }

  /**
   * 获取 合约Abi
   * @returns {Promise<AbiItem>}
   * @memberof EthExtract
   */
  async getAbi(): Promise<AbiItem> {
    const abi = await EthCoinType.getContractAbiJSON(this.contractAddress);
    return abi;
  }

  async send() {
    await this.getTokenClient();
    await this.init();
    const extractAddress = await EthConfig.getParam('extract_address');
    const tokenAmount = await this.contract.methods.balanceOf(extractAddress).call();
    const abiData = await this.contract.methods.transfer(this.transferToAddress, this.transferToAmount).encodeABI();
    let gasPrice = await EthTokenExtract.web3.eth.getGasPrice();
    const gasLimit = await this.contract.methods.transfer(this.transferToAddress, this.transferToAmount).estimateGas({
      from: extractAddress,
      to: this.contractAddress,
      value: 0,
      gasPrice,
    }).catch(error => {
      const transaction: TransactionMessage = {
        blockNum: null,
        hash: null,
        from: null,
        to: null,
        contract: this.contractAddress,
        amount: null,
        orderId: this.orderId
      }
      this.sendTransactionMessage(transaction);
    });

    if(new dec(tokenAmount).sub(this.transferToAmount).lt(0)){
      logger('ethExtract').warn(`${this.tokenName} 代币数量不足`);
      await Promise.resolve(`${this.tokenName} 代币数量不足`);
    }

    const number = await getAddressNonce(extractAddress);

    const rawTx = {
      nonce: toHex(number),
      gasPrice: toHex(gasPrice),
      gasLimit: toHex(gasLimit),
      to: this.contractAddress,
      value: 0,
      data: abiData
    }
    const tx = new Tx(rawTx);
    const extractAddressPrvateKey = await EthConfig.getParam('extract_address_private_key');
    tx.sign(Buffer.from(this.ethAddressPrivateKeyHandle(extractAddressPrvateKey), 'hex'));
    
    const serializedTx = tx.serialize();

    await this.sendSignedTransaction('0x' + serializedTx.toString('hex'));
  }

  async sendSignedTransaction(data) {
    EthTokenExtract.web3.eth.sendSignedTransaction(data)
    .on('transactionHash', hash => {
      const transaction: TransactionMessage = {
        blockNum: null,
        hash: hash,
        from: null,
        to: null,
        contract: this.contractAddress,
        amount: null,
        orderId: this.orderId
      };
      this.sendTransactionMessage(transaction);
      logger('ethExtract').trace(`${this.tokenName}提币 ${hash} amount ${new dec(this.transferToAmount).div(10**this.decimal).toString()}`);
    })
    .on('receipt', async receipt => {
      const TokenAmountWei = EthTokenExtract.web3.eth.abi.decodeParameter('uint256', receipt.logs[0].data);
      const amount = new dec(String(TokenAmountWei)).div(10**this.decimal);
      const to = EthTokenExtract.web3.eth.abi.decodeParameter('address', String(receipt.logs[0].topics[2]));
      
      const transaction: TransactionMessage = {
        blockNum: receipt.blockNumber,
        hash: receipt.transactionHash,
        from: receipt.from,
        to: String(to),
        contract: this.contractAddress,
        amount: amount.toString(),
        orderId: this.orderId
      }

      this.sendTransactionMessage(transaction);
      logger('ethExtract').trace(`${this.tokenName}提币 ${receipt.transactionHash} amount ${new dec(this.transferToAmount).div(10**this.decimal).toString()} 提取成功`);
      const coinTypeId = await EthCoinType.getCoinTypeId(this.contractAddress);
      EthTransfer.save(2, coinTypeId, transaction, '提币到用户地址');
    }).catch(error => {
      // console.log(error);
      const transaction: TransactionMessage = {
        blockNum: null,
        hash: null,
        from: null,
        to: null,
        contract: this.contractAddress,
        amount: null,
        orderId: this.orderId
      }
      this.sendTransactionMessage(transaction);
    }) 
  }

  /**
   * 发送交易通知到平台
   * @param {number} [num=1]
   * @memberof EthHandle
   */
  async sendTransactionMessage(transaction: TransactionMessage ,num: number = 1) {
    const sendUrl = await EthConfig.getParam('send_coin_callback_url');
    if(sendUrl) {
      post(sendUrl, {
        form: transaction
      }, (error, res, body) => {
        console.log(body);
        if(num < 5){
          if(error) {
            logger('ethHandle').debug(`${this.tokenName} 提币交易信息发送失败,检测到致命BUG。 ${num}`);
          }else{
            if(body === 'success') {
              logger('ethHandle').trace(`${this.tokenName} 提币交易信息发送成功---${transaction.hash} ${num}`);
            }else {
              logger('ethHandle').trace(`${this.tokenName} 提币交易信息发送失败---${transaction.hash} ${num}`);
              setTimeout(() => {
                this.sendTransactionMessage(transaction, num+=1)
              }, 5000)
            }
          }
        }
      });
    }
  }

  /**
   * 处理私钥
   * @param {string} privateKey
   * @returns
   * @memberof EthExtract
   */
  ethAddressPrivateKeyHandle(privateKey: string) {
    if(privateKey.includes('0x')){
      return privateKey.split('0x')[1];
    }
    return privateKey;
  }
}

export default EthTokenExtract;
